#pragma once

#include <iostream>
#include <queue>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>

using namespace std;

// 新需求: 我只想保存最新的5个任务，如果来了任务，老的任务，我想让他直接被丢弃

const uint32_t gDefaultCap = 5;

template <class T>
class BlockQueue
{
public:
    BlockQueue(uint32_t cap = gDefaultCap) : cap_(cap)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&conCond_, nullptr);
        pthread_cond_init(&proCond_, nullptr);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&conCond_);
        pthread_cond_destroy(&proCond_);
    }

public:
    //生产接口
    void push(const T &in) // const &: 纯输入
    {
        // 加锁
        // 判断->是否适合生产->bq是否为满->程序员视角的条件->1. 满(不生产) 2. 不满(生产)
        // if(满) 不生产,休眠
        // else if(不满) 生产，唤醒消费者
        // 解锁

        lockQueue();
        while (isFull()) // ifFull就是我们在临界区中设定的条件
        {
            // before: 当我等待的时候，会自动释放mutex_
            proBlockWait(); //阻塞等待，等待被唤醒。 被唤醒 ！= 条件被满足(概率虽然很小)，被唤醒 && 条件被满足
            // after: 当我醒来的时候，我是在临界区里醒来的！！
        }
        // while (isFull()) // ifFull就是我们在临界区中设定的条件
        // {
        //     //老的pop掉 但是不好打印
        //     T tmp = popCore();
        //     pushCore(in); 
        //     proBlockWait(); 
        // }
        // 条件满足，可以生产
        pushCore(in); //生产完成 
        // wakeupCon(); // 唤醒消费者
        unlockQueue();
        wakeupCon(); // 唤醒消费者
    }
    //消费接口
    T pop()
    {
        // 加锁
        // 判断->是否适合消费->bq是否为空->程序员视角的条件->1. 空(不消费) 2. 有(消费)
        // if(空) 不消费,休眠
        // else if(有) 消费，唤醒生产者
        // 解锁
        lockQueue();
        while (isEmpty())
        {
            conBlockwait(); //阻塞等待，等待被唤醒，？
        }
        // 条件满足，可以消费
        T tmp = popCore();
        unlockQueue();
        wakeupPro(); // 唤醒生产者

        return tmp;
    }
      bool isFull()
    {
        return bq_.size() == cap_;
    }

private:
    void lockQueue()
    {
        pthread_mutex_lock(&mutex_);
    }
    void unlockQueue()
    {
        pthread_mutex_unlock(&mutex_);
    }
    bool isEmpty()
    {
        return bq_.empty();
    }
  
    void proBlockWait() // 生产者一定是在临界区中的！
    {
        // 1. 在阻塞线程的时候，会自动释放mutex_锁
        pthread_cond_wait(&proCond_, &mutex_);
    }

    void conBlockwait() //阻塞等待，等待被唤醒
    {
        // 1. 在阻塞线程的时候，会自动释放mutex_锁
        pthread_cond_wait(&conCond_, &mutex_);
        // 2. 当阻塞结束，返回的时候，pthread_cond_wait，会自动帮你重新获得mutex_，然后才返回
        // 为什么我们上节课，写的代码，批量退出线程的时候，发现无法退出？
    }

    void wakeupPro() // 唤醒生产者
    {
        pthread_cond_signal(&proCond_);
    }
    void wakeupCon() // 唤醒消费者
    {
        pthread_cond_signal(&conCond_);
    }
    void pushCore(const T &in)
    {
        bq_.push(in); //生产完成
    }
    T popCore()
    {
        T tmp = bq_.front();
        bq_.pop();
        return tmp;
    }

private:
    uint32_t cap_;           //容量
    queue<T> bq_;            // blockqueue
    pthread_mutex_t mutex_;  //保护阻塞队列的互斥锁
    pthread_cond_t conCond_; // 让消费者等待的条件变量
    pthread_cond_t proCond_; // 让生产者等待的条件变量
};
